﻿using Core.Entities;
using Core.Enums;
using Core.ObjectOperate;
using Core.Tools;
using Core.Writer;
using System;
using System.Collections.Generic;

namespace Core.ReadWriter
{

    [Serializable]
    public class ObjectReadWriter : BaseReadWriter
    {
        private readonly Type Type;
        private readonly IObjectOperater ObjectOperater;
        private readonly CollectionEnumerable<string> FieldsEnumerable;

        internal ObjectReadWriter(Type Type, object Content, IObjectOperater ObjectOperater, RWSigns Sign) : base(Content, Sign)
        {
            this.Type = Type;
            this.ObjectOperater = ObjectOperater;

            FieldsEnumerable = new CollectionEnumerable<string>(Index =>
            {
                if (Index < 0)
                {
                    throw new IndexOutOfRangeException("Index : " + Index);
                }

                if ((Sign & RWSigns.Field) != 0)
                {
                    if (Index < ObjectOperater.FieldsCount)
                    {
                        return ObjectOperater.GetFieldName(Index);
                    }

                    Index -= ObjectOperater.FieldsCount;
                }

                if ((Sign & RWSigns.Property) != 0 && Index < ObjectOperater.PropertiesCount)
                {
                    return ObjectOperater.GetPropertyName(Index);
                }

                throw new IndexOutOfRangeException("Index");
            }, Count, true);

            Reset();
        }

        public ObjectReadWriter(object Content, RWSigns Sign = RWSigns.ChangeType | RWSigns.Property) : base(Content, Sign)
        {
            Type = Content.GetType();
            ObjectOperater = ObjectOperate.ReflectionImpl.ObjectOperater.Create(Type, (Sign & RWSigns.NonPublic) != 0);
        }

        public ObjectReadWriter(object Content, IObjectOperater ObjectOperater, RWSigns Sign = RWSigns.ChangeType | RWSigns.Property) : base(Content, Sign)
        {
            if (ObjectOperater == null)
            {
                throw new NullReferenceException("ObjectOperater");
            }

            this.ObjectOperater = ObjectOperater;
            Type = ObjectOperater.Type;
        }

        private void InternalSetFieldValue(int Index, object Value)
        {
            Value = AsSetValue(Value);

            if ((Sign & RWSigns.ChangeType) != 0)
            {
                var Type = ObjectOperater.GetFieldType(Index);

                if (!Type.IsInstanceOfType(Value))
                {
                    Value = Convert.ChangeType(Value, Type);
                }
            }

            ObjectOperater.SetFieldValue(InternalContent, Index, Value);
        }

        private void InternalSetPropertyValue(int Index, object Value)
        {
            Value = AsSetValue(Value);

            if ((Sign & RWSigns.ChangeType) != 0)
            {
                var Type = ObjectOperater.GetPropertyType(Index);

                if (!Type.IsInstanceOfType(Value))
                {
                    Value = Convert.ChangeType(Value, Type);
                }
            }

            ObjectOperater.SetPropertyValue(InternalContent, Index, Value);
        }

        public override object this[string Name]
        {
            get
            {
                if (Name == null)
                {
                    throw new NullReferenceException("Name");
                }

                int Index;

                if ((Sign & RWSigns.Field) != 0 && (Index = ObjectOperater.GetFieldIndex(Name)) != -1)
                {
                    return AsGetValue(ObjectOperater.GetFieldValue(InternalContent, Index));
                }

                if ((Sign & RWSigns.Property) != 0 && (Index = ObjectOperater.GetPropertyIndex(Name)) != -1)
                {
                    return AsGetValue(ObjectOperater.GetPropertyValue(InternalContent, Index));
                }

                throw new KeyNotFoundException(Name);
            }
            set
            {
                if (Name == null)
                {
                    throw new NullReferenceException("Name");
                }

                int Index;

                if ((Sign & RWSigns.Field) != 0 && (Index = ObjectOperater.GetFieldIndex(Name)) != -1)
                {
                    InternalSetFieldValue(Index, value);
                }
                else if ((Sign & RWSigns.Property) != 0 && (Index = ObjectOperater.GetPropertyIndex(Name)) != -1)
                {
                    InternalSetPropertyValue(Index, value);
                }
                else
                {
                    throw new KeyNotFoundException(Name);
                }
            }
        }

        public override object this[int Index]
        {
            get
            {
                if ((Sign & RWSigns.Field) != 0)
                {
                    if (Index >= 0 && Index < ObjectOperater.FieldsCount)
                    {
                        return AsGetValue(ObjectOperater.GetFieldValue(InternalContent, Index));
                    }

                    int PropertyIndex = Index - ObjectOperater.FieldsCount;

                    if ((Sign & RWSigns.Property) != 0 && PropertyIndex >= 0 && PropertyIndex < ObjectOperater.PropertiesCount)
                    {
                        return AsGetValue(ObjectOperater.GetPropertyValue(InternalContent, PropertyIndex));
                    }
                }
                else if ((Sign & RWSigns.Property) != 0 && Index >= 0 && Index < ObjectOperater.PropertiesCount)
                {
                    return AsGetValue(ObjectOperater.GetPropertyValue(InternalContent, Index));
                }

                throw new IndexOutOfRangeException("Index : " + Index);
            }
            set
            {
                if ((Sign & RWSigns.Field) != 0)
                {
                    if (Index >= 0 && Index < ObjectOperater.FieldsCount)
                    {
                        InternalSetFieldValue(Index, value);

                        return;
                    }

                    int PropertyIndex = Index - ObjectOperater.FieldsCount;

                    if ((Sign & RWSigns.Property) != 0 && PropertyIndex >= 0 && PropertyIndex < ObjectOperater.PropertiesCount)
                    {
                        InternalSetPropertyValue(PropertyIndex, value);

                        return;
                    }
                }
                else if ((Sign & RWSigns.Property) != 0 && Index >= 0 && Index < ObjectOperater.PropertiesCount)
                {
                    InternalSetPropertyValue(Index, value);

                    return;
                }

                throw new IndexOutOfRangeException("Index : " + Index);
            }
        }

        public override IEnumerable<string> Fields { get { return FieldsEnumerable; } }

        public override int Count
        {
            get
            {
                int Result = 0;

                if ((Sign & RWSigns.Field) != 0)
                {
                    Result += ObjectOperater.FieldsCount;
                }

                if ((Sign & RWSigns.Property) != 0)
                {
                    Result += ObjectOperater.PropertiesCount;
                }

                return Result;
            }
        }

        public override RWTypes ObjectType
        {
            get
            {
                return RWTypes.Object;
            }
            set
            {
                if (value != RWTypes.Object)
                {
                    throw new NotSupportedException("ObjectReadWriter ObjectType only be object.");
                }
            }
        }

        public override void Add(string Name, object Value)
        {
            this[Name] = Value;
        }

        public override IDataWriter CreateChildrenWriter(CreateChildrenWriterInfo Info)
        {
            if (Info.Name == null && Info.Index == null)
            {
                throw new Exception("Name/Index cannot be null.");
            }

            int Index;

            if (Info.Name != null)
            {
                if ((Sign & RWSigns.Field) != 0 && (Index = ObjectOperater.GetFieldIndex(Info.Name)) != -1)
                {
                    return Create(ObjectOperater.GetFieldType(Index), Sign);
                }

                if ((Sign & RWSigns.Property) != 0 && (Index = ObjectOperater.GetPropertyIndex(Info.Name)) != -1)
                {
                    return Create(ObjectOperater.GetPropertyType(Index), Sign);
                }

                throw new KeyNotFoundException(Info.Name);
            }
            else
            {
                Index = Info.Index.Value;

                if (Index < 0)
                {
                    throw new IndexOutOfRangeException("Index : " + Index);
                }

                if ((Sign & RWSigns.Field) != 0)
                {
                    if (Index < ObjectOperater.FieldsCount)
                    {
                        return Create(ObjectOperater.GetFieldType(Index), Sign);
                    }

                    Index -= ObjectOperater.FieldsCount;
                }

                if (Index < ObjectOperater.PropertiesCount)
                {
                    return Create(ObjectOperater.GetPropertyType(Index), Sign);
                }

                throw new IndexOutOfRangeException("Index : " + Index);
            }
        }

        public override bool TryGetValue(string Name, out object Value)
        {
            if (Name == null)
            {
                throw new NullReferenceException("Name");
            }

            int Index;

            if ((Sign & RWSigns.Field) != 0 && (Index = ObjectOperater.GetFieldIndex(Name)) != -1)
            {
                Value = AsGetValue(ObjectOperater.GetFieldValue(Content, Index));

                return true;
            }

            if ((Sign & RWSigns.Property) != 0 && (Index = ObjectOperater.GetPropertyIndex(Name)) != -1)
            {
                Value = AsGetValue(ObjectOperater.GetPropertyValue(Content, Index));

                return true;
            }

            Value = null;

            return false;
        }
    }
}